package com.feng.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.feng.entity.*;
import com.feng.entity.vo.*;
import com.feng.exception.SystemException;
import com.feng.mapper.MenuMapper;
import com.feng.service.AccountRoleService;
import com.feng.service.MenuService;
import com.feng.utils.Const;
import com.feng.utils.HttpCodeEnum;
import com.feng.utils.WebUtils;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * (Menu)表服务实现类
 *
 * @author makejava
 * @since 2024-10-14 13:45:21
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    final
    AccountRoleService accountRoleService;

    public MenuServiceImpl(AccountRoleService accountRoleService) {
        this.accountRoleService = accountRoleService;
    }

    @Override
    public RestResponse selectRouterMenuTreeByUserId(String id) {
        List<Menu> menus = null;
        MenuMapper menuMapper = getBaseMapper();
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        LambdaQueryWrapper<AccountRole> arWrapper = new LambdaQueryWrapper<>();
        arWrapper.eq(AccountRole::getAccountId, user.getAccount().getId());
        AccountRole accountRole = accountRoleService.getOne(arWrapper);
        if (user.getAccount().getType() == Const.ONE || accountRole.getRoleId() == 1) {
            menus = menuMapper.selectRouterMenuTreeByAdmin();
        } else {
            menus = menuMapper.selectRouterMenuTreeByUserId(id);
        }

        List<MenuVo> menuVos = new ArrayList<>();
        menus.forEach(menu -> menuVos.add(new MenuVo(menu.getId(), menu.getMenuName(), menu.getParentId(), menu.getOrderNum(),
                menu.getPath(), menu.getComponent(), menu.getMenuType(), menu.getVisible(), menu.getStatus(), menu.getPermission(),
                menu.getIcon(), menu.getCreateTime(), null)));
        List<MenuVo> menuVoList = buildMenuTree(menuVos, 0L);
        return RestResponse.success(menuVoList);
    }

    @Override
    public RestResponse selectMenuTree() {
        List<Menu> menus = list();
        List<MenuTreeVo> menuTree = new ArrayList<>();
        menus.stream()
                .forEach(menu -> {
                    menuTree.add(new MenuTreeVo(menu.getId(), menu.getMenuName(), menu.getParentId(), null));
                });
        List<MenuTreeVo> vos = adminMenuTree(menuTree, 0L);
        return RestResponse.success(vos);
    }

    @Override
    public RestResponse listAll(String status, String menuName) {
        LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.hasText(status), Menu::getStatus, status);
        wrapper.like(StringUtils.hasText(menuName), Menu::getMenuName, menuName);
        wrapper.orderByAsc(Menu::getParentId, Menu::getOrderNum);
        List<Menu> menus = list(wrapper);
        List<AdminMenuVo> adminMenuVos = new ArrayList<>();
        menus.forEach(menu -> {
            AdminMenuVo adminMenuVo = new AdminMenuVo();
            BeanUtils.copyProperties(menu, adminMenuVo);
            adminMenuVos.add(adminMenuVo);
        });
        return RestResponse.success(adminMenuVos);
    }

    @Override
    public RestResponse addMenu(Menu menu) {
        LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
        if (menuNameExist(wrapper, menu)) throw new SystemException(HttpCodeEnum.MENU_NAME_EXIST);
        if (StringUtils.hasText(menu.getPath())){
            if(!menu.getPath().startsWith("/")){
                String newPath = "/" + menu.getPath();
                menu.setPath(newPath);
            }
            if (pathExist(wrapper, menu)) throw new SystemException(HttpCodeEnum.MENU_PATH_EXIST);
        }
        if (StringUtils.hasText(menu.getComponent()))
            if (componentExist(wrapper, menu)) throw new SystemException(HttpCodeEnum.MENU_COMPONENT_EXIST);
        getBaseMapper().AUTO_INCREMENT();
        save(menu);
        return RestResponse.success();
    }

    @Override
    public RestResponse getMenuById(Long id) {
        Menu menu = getById(id);
        AdminMenuVo adminMenuVo = new AdminMenuVo();
        BeanUtils.copyProperties(menu, adminMenuVo);
        adminMenuVo.setCreateTime(null);
        return RestResponse.success(adminMenuVo);
    }

    @Override
    public RestResponse updateMenu(Menu menu) {
        if (StringUtils.hasText(menu.getPath())){
            if(!menu.getPath().startsWith("/")){
                String newPath = "/" + menu.getPath();
                menu.setPath(newPath);
            }
        }
        if(menu.getId().equals(menu.getParentId()))
            throw new SystemException(HttpCodeEnum.SUPPER_MENU_CANNOT_SELECT_ITSELF);
        updateById(menu);
        return RestResponse.success();
    }

    @Override
    public RestResponse deleteMenu(Long id) {
        LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Menu::getParentId, id);
        List<Menu> menus = list(wrapper);
        if (!menus.isEmpty()) throw new SystemException(HttpCodeEnum.SUBMENU_EXIST_CANNOT_DELETE);
        removeById(id);
        return RestResponse.success();
    }

    @Override
    public void export(HttpServletResponse response) {
        try (ServletOutputStream outputStream = response.getOutputStream()){
            WebUtils.excelResponseHeader(response, "菜单");
            List<Menu> menus = list();
            List<MenuExportVo> exportVo = new ArrayList<>();
            menus.forEach(menu -> exportVo.add(new MenuExportVo(menu.getId(), menu.getMenuName(), menu.getParentId(), menu.getOrderNum(),
                    menu.getPath(), menu.getComponent(), menu.getMenuType(), menu.getVisible(), menu.getStatus(), menu.getPermission(),
                    menu.getIcon(), menu.getCreateTime())));
            EasyExcel.write(outputStream, MenuExportVo.class)
                    .autoCloseStream(Boolean.FALSE).sheet("菜单导出")
                    .doWrite(exportVo);
        } catch (Exception e) {
            response.reset();
            WebUtils.renderString(response, RestResponse.failure(500, "菜单导出出现了错误").asJsonString());
        }
    }

    private boolean componentExist(LambdaQueryWrapper<Menu> wrapper, Menu menu) {
        wrapper.eq(Menu::getComponent, menu.getComponent());
        long count = count(wrapper);
        wrapper.clear();
        return count > 0;
    }

    private boolean pathExist(LambdaQueryWrapper<Menu> wrapper, Menu menu) {
        wrapper.eq(Menu::getPath, menu.getPath());
        long count = count(wrapper);
        wrapper.clear();
        return count > 0;
    }

    private boolean menuNameExist(LambdaQueryWrapper<Menu> wrapper, Menu menu) {
        wrapper.eq(Menu::getMenuName, menu.getMenuName());
        long count = count(wrapper);
        wrapper.clear();
        return count > 0;
    }

    private List<MenuTreeVo> adminMenuTree(List<MenuTreeVo> menuTreeVos, long parentId) {
        return menuTreeVos.stream().parallel()
                .filter(menuTreeVo -> menuTreeVo.getParentId().equals(parentId))
                .map(menuTreeVo -> {
                    return menuTreeVo.setChildren(getTreeChildren(menuTreeVos, menuTreeVo.getId()));
                })
                .collect(Collectors.toList());
    }

    private List<MenuTreeVo> getTreeChildren(List<MenuTreeVo> menuTreeVos, Long parentId) {
        return menuTreeVos.stream().parallel()
                .filter(menuTreeVo -> menuTreeVo.getParentId().equals(parentId))
                .map(menuTreeVo -> menuTreeVo.setChildren(getTreeChildren(menuTreeVos, menuTreeVo.getId())))
                .collect(Collectors.toList());
    }

    private List<MenuVo> buildMenuTree(List<MenuVo> menuVos, Long id) {
        return menuVos.stream().parallel()
                .filter(menuVo -> menuVo.getParentId().equals(id))
                .map(menuVo -> menuVo.setChildren(buildMenuTree(menuVos, menuVo.getId())))
                .collect(Collectors.toList());
    }
}

